package org.aspectsense.middleware.adaptation;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Stack;

import org.aspectsense.middleware.ComponentContainer;
import org.aspectsense.middleware.Repository;


/**
 * BigraphTree representation of installed components and services
 * @author Costas
 *
 */


public class BigraphAdaptationTree 
{

	private  LinkedList <BigraphComponentNode> tree = new LinkedList<BigraphComponentNode>();

	private BigraphComponentNode treeRoot;
	private ComponentContainer root;
	private String endUserService;
	
	public BigraphAdaptationTree (ComponentContainer root, String endUserService)
	{
		this.root= root;
		this.endUserService = endUserService;
		tree = new LinkedList<BigraphComponentNode>();
		treeRoot = new BigraphComponentNode (root,null,endUserService);
		buildTree ();
		root.resolve(resolveTree ());
		//printTree();
	}
	
	public boolean isResolved ()
	{
		return root.isResolved();
	}
	
	/**
	 * 
	 * @return true if there is at least one provider for each required service that it is resolved, false otherwise
	 */
	private boolean resolveTree() 
	{
		String[] endUserRequirements = root.getRequiredServicesClassNames();
		int reqs = endUserRequirements.length;
		
		for (String s:endUserRequirements)
		{
			for (ComponentContainer cc:Repository.getServiceProviders(s))
			{
				if (cc.isResolved())
				{
					reqs--;
					break;
				}
			}
		}
		
		if (reqs==0)
			return true;
		else
			return false;
		
		
	}

	/**
	 * 	# Step 1: Push the root node in the Stack.
	 *	# Step 2: Loop until stack is empty.
	 *	# Step 3: Peek the node of the stack.
	 *	# Step 4: If the node does not have any unvisited child nodes, pop the node from the stack.
	 *	# Step 5: Else get head's children and create tree nodes 
	**/
	public void buildTree ()
	{
		Stack <BigraphComponentNode> stack = new Stack <BigraphComponentNode> ();
		stack.push(treeRoot); //#1
		BigraphComponentNode head, child;
		ArrayList <BigraphComponentNode> visitedNodes = new ArrayList <BigraphComponentNode> ();
		ArrayList <String> providedServices = new ArrayList<String>();
		ComponentContainer cc, p;
		String [] reqServices;
		LinkedList <ComponentContainer> providers;
		Iterator <ComponentContainer> providersItr;
	
		
		
		while (!stack.empty()) //#2
		{
			
			head = stack.peek(); //#3
			
			//backtracking
			if (visitedNodes.contains(head))
			{
				if (head.getContainer().isResolved())
					tree.add (stack.pop());
				else
					stack.pop();
				continue;
			}
			
			//Get the children and for each one of them
			//create new tree node with the head as their parent
			cc = head.getContainer();
			reqServices = cc.getRequiredServicesClassNames();
			if (reqServices==null || reqServices.length==0)
			{
				
				cc.resolve(true);
				//System.out.println ("No req services for "+cc.getBundleName());
				
			}
			else
			{
				for (String s:reqServices)
				{
					providers = Repository.getServiceProviders(s);
					if (providers!=null && providers.size()>0)
					{
						providersItr = providers.iterator();
						while (providersItr.hasNext())
						{
							p = providersItr.next();
							if (isSafe (p,providedServices))
							{
								child = new BigraphComponentNode (p, head, s);
								head.addChildren (s,child);
								stack.push(child);
								for (String provs:child.getContainer().getProvidedServicesClassNames())
								{
									providedServices.add(provs);
								}
								
							}
							else {
								System.out.println (p.getBundleName()+ " caused cycle!");
							}
							
						}
						//This means that all the existing providers for the required service
						//are causing a cycle. If a component cannot have a provider for
						//all the required services it cannot be resolved
						if (head.getChildren(s)==null) 
						{
							cc.resolve(false);
							
							//System.out.println ("Unresolving "+cc.getBundleName());
							break;
						}
						else
						{
							//System.out.println ("Resolving "+cc.getBundleName());
							cc.resolve(true);
						}
					}
					else //Although the provider has required services nobody provides them
					{
						cc.resolve(false);
						
						System.out.println ("Unresolving "+cc.getBundleName());
						break;
					}
					
				}
			}
			
			visitedNodes.add(head);
			
		}
		
		
	}
	
	/**
	 * This method will return false if the given provider requires a service
	 * provided by a node already in the stack => cycle
	 * @param p
	 * @param providedServices
	 * @return
	 */
	private boolean isSafe(ComponentContainer p, ArrayList<String> providedServices) 
	{
		if (providedServices==null || providedServices.size()<=0)
			return true;
		else
		{
			for (String s:p.getRequiredServicesClassNames())
			{
				if (providedServices.contains(s))
					return false;
			}
		}
		return true;
	}

	/**
	 * Prints the tree - just for test
	 */
	private void printTree() 
	{
		BigraphComponentNode currNode=null, prevNode=null;
		
		
		prevNode = tree.getLast();
		for (int i=tree.size()-1; i>=0; i--)
		{
			currNode = tree.get(i);
			
			if ( currNode.getParent()==null)
				System.out.print (currNode.getShortComponentName() +"(R)");
			else if (prevNode.getParent()==currNode.getParent())
			{
				
				System.out.print (" -- "+currNode.getShortComponentName());
			}
			else
			{
				System.out.println ();
				System.out.print (currNode.getShortComponentName());
			}
				
			prevNode = currNode;
			
		}
		System.out.println ();
		
	}
	
	public BigraphComponentNode getTreeRoot ()
	{
		return this.treeRoot;
	}
	
	public ComponentContainer getRoot ()
	{
		return this.root;
	}
	
	public LinkedList <BigraphComponentNode> getTree ()
	{
		return this.tree;
	}
	

}
